package tr.helper.controller.user;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import io.vavr.Tuple3;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.stream.Collectors;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.control.Label;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.CheckBoxTreeTableCell;
import javafx.util.Callback;
import lombok.var;
import org.apache.commons.lang3.StringUtils;
import tr.helper.CommonUtil;
import tr.helper.auth.GoogleAuthenticatorUtils;
import tr.helper.controller.Context;
import tr.helper.controller.SecurityController;
import tr.helper.controller.home.HomeController;
import tr.helper.controller.log.LogController;
import tr.helper.controller.wine.WineController;
import tr.helper.domain.TransferAmount;
import tr.helper.domain.TransferUResponse;
import tr.helper.repository.dao.UserDao;
import tr.helper.repository.table.User;
import tr.helper.service.LoginErrorException;
import tr.helper.service.TrService;
import tr.helper.view.TransferItem;
import tr.helper.view.UserItem;
import tr.helper.view.WineUserItem;

public class UserListController extends SecurityController {

    @FXML
    private TreeTableView<UserItem> tblUser;

    @FXML
    private TableView<TransferItem> ttvTransfer;

    @FXML
    private Label lbTotal;

    @FXML
    private Label lbTotal2;

    @FXML
    private Label lbCalc;

    @FXML
    private CheckBox checkLocal;

    @FXML
    private TextField txtPassword;

    @FXML
    private Button updatePwd;

    @FXML
    private TextField txtDate;

    @FXML
    private Button btnConvertU;

    @FXML
    private Button btnUToParent;

    @FXML
    private Button btnWToTarget;

    @FXML
    private Button btnGetTransferLog;

    @FXML
    private TextField txtTarget;

    private TreeItem<UserItem> root;

    private TrService trService = new TrService();

    private UserDao userDao = new UserDao();

    private BigDecimal total = BigDecimal.ZERO;
    private Map<String, BigDecimal> totalGroup = Maps.newHashMap();

    @Override
    public void init(URL location, ResourceBundle resources) {
        bindData();
        ttvTransfer.getColumns().addAll(CommonUtil.getTableCols(TransferItem.class));
    }

    @FXML
    public void doGetVerifyCode() throws Exception {
        var items = tblUser.getSelectionModel().getSelectedItems();
        if (CollectionUtil.isEmpty(items) || items.size() > 1) {
            CommonUtil.alert(null, "必须且只能选择1个用户!");
            return;
        }
        HomeController home = (HomeController) Context.controllers.get(HomeController.class.getSimpleName());
        if (null == home) {
            CommonUtil.alert(null, "主窗口不能关闭!");
            return;
        }
        home.doOpenLog();

        LogController log = (LogController) Context.controllers.get(LogController.class.getSimpleName());
        UserItem user = items.get(0).getValue();

        Service<Double> service = new Service<Double>() {
            @Override
            protected Task<Double> createTask() {
                return new Task<Double>() {
                    @Override
                    protected Double call() throws MalformedURLException {
                        Tuple3<HttpResponse, String, String> tuple3;
                        try {
                            //getCode.setDisable(true);
                            log.doClear();
                            tuple3 = getCookie(user, log, true);
                            getVerifyCode(tuple3, log);
                            //getCode.setDisable(false);
                        } catch (LoginErrorException le) {
                            tuple3 = getCookie(user, log, true);
                            getVerifyCode(tuple3, log);
                            //getCode.setDisable(false);
                        } catch (Exception e) {
                            //getCode.setDisable(false);
                        }
                        return null;
                    }
                };
            }
        };
        Platform.runLater(service::restart);
    }

    private void getVerifyCode(Tuple3<HttpResponse, String, String> tuple3, LogController log) {
        // 获取验证码
        JSONObject sendData = trService.getVerifyCode(tuple3._3(), tuple3._2());
        log.addLog("验证码接口返回:%s", sendData.getString("showText"));
    }

    @FXML
    public void doUpdatePwd() throws Exception {
        String password = txtPassword.getText();
        if (StringUtils.length(password) < 6) {
            CommonUtil.alert(null, "密码必须包含数字字面大小写！");
            return;
        }

        var items = tblUser.getSelectionModel().getSelectedItems();
        if (CollectionUtil.isEmpty(items)) {
            CommonUtil.alert(null, "你先选一个行么?");
            return;
        }

        HomeController home = (HomeController) Context.controllers.get(HomeController.class.getSimpleName());
        if (null == home) {
            CommonUtil.alert(null, "主窗口不能关闭!");
            return;
        }
        home.doOpenLog();

        LogController log = (LogController) Context.controllers.get(LogController.class.getSimpleName());

        for (TreeItem<UserItem> userItem : items) {
            final UserItem user = userItem.getValue();
            Service<Double> service = new Service<Double>() {
                @Override
                protected Task<Double> createTask() {
                    return new Task<Double>() {
                        @Override
                        protected Double call() throws MalformedURLException {
                            Tuple3<HttpResponse, String, String> tuple3;
                            try {
                                updatePwd.setDisable(true);
                                log.doClear();
                                if (checkLocal.isSelected()) {
                                    User updUser = new User();
                                    updUser.setPassword(password);
                                    userDao.updateByPrimaryKey(updUser, user.getId());
                                    log.addLog("修改本地密码成功");
                                } else {
                                    tuple3 = getCookie(user, log, true);
                                    syncPassword(tuple3, log, password, GoogleAuthenticatorUtils.getTOTPCode(user.getSec()), user.getId());
                                }
                                updatePwd.setDisable(false);
                            } catch (LoginErrorException le) {
                                tuple3 = getCookie(user, log, true);
                                syncPassword(tuple3, log, password, GoogleAuthenticatorUtils.getTOTPCode(user.getSec()), user.getId());
                            } catch (Exception e) {
                                log.addLog("修改失败:%s", e.getMessage());
                                updatePwd.setDisable(false);
                            }
                            return null;
                        }
                    };
                }
            };
            Platform.runLater(service::restart);
        }
    }

    private void syncPassword(Tuple3<HttpResponse, String, String> tuple3, LogController log, String password, String verifyCode, Long userId) {
        JSONObject updateData = trService.updatePassword(tuple3._3(), tuple3._2(), password, verifyCode);
        if ("0".equals(updateData.getString("result"))) {
            User updUser = new User();
            updUser.setPassword(password);
            userDao.updateByPrimaryKey(updUser, userId);
            log.addLog("修改远程密码成功，并同步到本地");
            Platform.runLater(UserListController.this::bindData);
        } else {
            log.addLog("远程修改返回:%s", updateData.getString("showText"));
        }
    }

    @FXML
    public void doCols() {
    }

    @FXML
    public void doSelect() {
        var items = tblUser.getSelectionModel().getSelectedItems();
        if (CollectionUtil.isEmpty(items)) {
            CommonUtil.alert(null, "你先选一个行么?");
            return;
        }

        if (CommonUtil.mode == 1) {
            ObservableList<UserItem> users = FXCollections.observableArrayList();
            HomeController homeController = (HomeController) Context.controllers.get(HomeController.class.getSimpleName());
            items.stream().filter(i -> i.getValue().getId() != 1L).forEach(item -> {
                users.add(item.getValue());
            });
            homeController.putUsers(users);
            homeController.userStage.close();
        } else {
            ObservableList<WineUserItem> users = FXCollections.observableArrayList();
            WineController wineController = (WineController) Context.controllers.get(WineController.class.getSimpleName());
            items.stream().filter(i -> i.getValue().getId() != 1L).forEach(item -> {
                users.add(BeanUtil.copyProperties(item.getValue(), WineUserItem.class));
            });
            wineController.putUsers(users);
            wineController.userStage.close();
        }
    }

    @FXML
    public void doUToParent() throws Exception {
        var items = tblUser.getSelectionModel().getSelectedItems();
        if (CollectionUtil.isEmpty(items)) {
            CommonUtil.alert(null, "选择一个要向上转的账号?");
            return;
        }
        HomeController homeController = (HomeController) Context.controllers.get(HomeController.class.getSimpleName());
        homeController.doOpenLog();
        LogController log = (LogController) Context.controllers.get(LogController.class.getSimpleName());

        btnUToParent.setDisable(true);
        Service<Double> service = new Service<Double>() {
            @Override
            protected Task<Double> createTask() {
                return new Task<Double>() {
                    @Override
                    protected Double call() {
                        JSONObject jsonObject;
                        var parents = items.stream().filter(a -> a.getValue().getParentId() == 0L).collect(Collectors.toList());
                        for (TreeItem<UserItem> item : items) {
                            UserItem userItem = item.getValue();
                            var parentOption = parents.stream().filter(p -> p.getValue().getId().equals(userItem.getParentId())).findFirst();
                            if (parentOption.isPresent()) {
                                String target = parentOption.get().getValue().getUsername();
                                try {
                                    HttpResponse response = trService
                                        .transferU(userItem.getCookie(), userItem.getToken(), GoogleAuthenticatorUtils.getTOTPCode(userItem.getSec()), userItem.getPassword(), target,
                                            userItem.getBalanceU());
                                    jsonObject = JSON.parseObject(response.body());
                                    log.addLog("账号: %s 转让U钱包: %s 美金到 上级账号: %s %s", userItem.getUsername(), userItem.getBalanceU(), target, jsonObject.getString("showTittle"));
                                    if ("0".equals(jsonObject.getString("result"))) {
                                        User updUser = new User();
                                        updUser.setBalanceU(BigDecimal.ZERO);
                                        userDao.updateByPrimaryKey(updUser, userItem.getId());
                                        userItem.setBalanceU(BigDecimal.ZERO);
                                        log.addLog("账号: %s 本地数据更新成功", userItem.getUsername());
                                    }
                                } catch (Exception e) {
                                    log.addLog("账号: %s U转 %s %s", userItem.getUsername(), target, e.getMessage());
                                }
                            }
                        }
                        btnUToParent.setDisable(false);
                        return Double.valueOf(0);
                    }
                };
            }
        };
        Platform.runLater(service::restart);
    }

    @FXML
    public void doWToTarget() throws Exception {
        String target = txtTarget.getText();
        if (StringUtils.isBlank(target)) {
            CommonUtil.alert(null, "你老人家请填写目标账号?");
            return;
        }
        var items = tblUser.getSelectionModel().getSelectedItems();
        if (CollectionUtil.isEmpty(items)) {
            CommonUtil.alert(null, "你老人家选一个要转的用户行么?");
            return;
        }
        if (CommonUtil.confirm(null, "操作确认", "确定要转给账号:" + target + "?")) {
            HomeController homeController = (HomeController) Context.controllers.get(HomeController.class.getSimpleName());
            homeController.doOpenLog();
            LogController log = (LogController) Context.controllers.get(LogController.class.getSimpleName());

            btnWToTarget.setDisable(true);
            Service<Double> service = new Service<Double>() {
                @Override
                protected Task<Double> createTask() {
                    return new Task<Double>() {
                        @Override
                        protected Double call() {
                            JSONObject jsonObject;
                            for (TreeItem<UserItem> item : items) {
                                UserItem userItem = item.getValue();
                                try {
                                    HttpResponse response = trService
                                        .transferU(userItem.getCookie(), userItem.getToken(), GoogleAuthenticatorUtils.getTOTPCode(userItem.getSec()), userItem.getPassword(), target,
                                            userItem.getBalanceU());
                                    jsonObject = JSON.parseObject(response.body());
                                    if (null == jsonObject.getString("showTitle")) {
                                        response = trService
                                            .transferU2(userItem.getCookie(), userItem.getToken(), GoogleAuthenticatorUtils.getTOTPCode(userItem.getSec()), userItem.getPassword(), target,
                                                userItem.getBalanceU());
                                        jsonObject = JSON.parseObject(response.body());
                                    }
                                    log.addLog("账号: %s 转让U钱包: %s 美金到 账号: %s %s", userItem.getUsername(), userItem.getBalanceU(), target, jsonObject.getString("showTittle"));
                                    if ("0".equals(jsonObject.getString("result"))) {
                                        User updUser = new User();
                                        updUser.setBalanceU(BigDecimal.ZERO);
                                        userDao.updateByPrimaryKey(updUser, userItem.getId());
                                        userItem.setBalanceU(BigDecimal.ZERO);
                                        log.addLog("账号: %s 本地数据更新成功", userItem.getUsername());
                                    }
                                } catch (Exception e) {
                                    log.addLog("账号: %s U转 %s %s", userItem.getUsername(), target, e.getMessage());
                                }
                            }
                            btnWToTarget.setDisable(false);
                            return Double.valueOf(0);
                        }
                    };
                }
            };
            Platform.runLater(service::restart);
        }
    }

    private ObservableList<TreeItem<UserItem>> getSelectedUserItems() {
        var items = tblUser.getSelectionModel().getSelectedItems();
        if (CollectionUtil.isEmpty(items)) {
            CommonUtil.alert(null, "你老人家选一个要转的用户行么?");
            return null;
        }
        return items;
    }

    @FXML
    public void doGetTransferLog() {
        var items = getSelectedUserItems();
        if (null == items) {
            return;
        }
        var startDate = txtDate.getText();
        var dateList = StringUtils.split(startDate, "/");
        btnGetTransferLog.setDisable(true);
        ttvTransfer.getItems().clear();
        Service<Double> service = new Service<Double>() {
            @Override
            protected Task<Double> createTask() {
                return new Task<Double>() {
                    @Override
                    protected Double call() {
                        String target = txtTarget.getText();
                        List<TransferItem> transferItems = new ArrayList<>();
                        for (TreeItem<UserItem> item : items) {
                            TransferUResponse response;
                            try {
                                response = trService.getTransferU(item.getValue().getCookie());
                                if (null != response && CollectionUtil.isNotEmpty(response.getData())) {
                                    for (TransferAmount amount : response.getData()) {
                                        TransferItem transferItem;
                                        if (StringUtils.isNotBlank(target) && !StringUtils.contains(amount.getRemark(), target)) {
                                            continue;
                                        }
                                        if (checkLocal.isSelected()) {
                                            if (StringUtils.contains(amount.getActionType(), "Transfer to U")) {
                                                continue;
                                            }
                                        } else {
                                            if (StringUtils.contains(amount.getActionType(), "Transfer from U")) {
                                                continue;
                                            }
                                        }

                                        transferItem = BeanUtil.copyProperties(amount, TransferItem.class);
                                        transferItem.setMaster(item.getValue().getMaster());
                                        if (StringUtils.isNotBlank(startDate)) {
                                            DateTime start = DateUtil.parse(startDate, "yyyy-MM-dd");
                                            DateTime transferDate = DateUtil.parse(transferItem.getCreated(), "yyyy-MM-dd");
                                            if (dateList.length == 1 && transferDate.isAfter(start)) {
                                                transferItems.add(transferItem);
                                            }
                                            if (dateList.length > 1 && transferDate.isAfter(start) && transferDate.isBeforeOrEquals(DateUtil.parse(dateList[1], "yyyy-MM-dd"))) {
                                                transferItems.add(transferItem);
                                            }
                                        } else {
                                            transferItems.add(transferItem);
                                        }

                                    }
                                }
                            } catch (Exception e) {
                                CommonUtil.alert(null, String.format("账号: %s 获取交易异常 %s", item.getValue().getUsername(), e.getMessage()));
                            }
                        }
                        ttvTransfer.getItems().addAll(transferItems);

                        BigDecimal totalU = BigDecimal.ZERO;
                        Map<String, BigDecimal> totalMap = new HashMap<>();
                        for (TransferItem transferItem : transferItems) {
                            BigDecimal value = CommonUtil.convertDecimal(transferItem.getUAmount());
                            if (totalMap.get(transferItem.getMaster()) == null) {
                                totalMap.put(transferItem.getMaster(), value);
                            } else {
                                totalMap.put(transferItem.getMaster(), totalMap.get(transferItem.getMaster()).add(value));
                            }
                            totalU = totalU.add(value);
                        }

                        JSONObject jsonObject = trService.getCNY();
                        String totalText = String.format("$:%s(￥:%s) 汇率:%s", totalU, convertCny(jsonObject, totalU), jsonObject.get("rate"));
                        StringBuilder text = new StringBuilder();
                        totalMap.forEach((k, amountU) -> {
                            text.append(String.format("%s:$:%s(￥%s)", k, amountU, convertCny(jsonObject, amountU)));
                        });
                        Platform.runLater(() -> {
                            lbTotal2.setText(text.toString());
                            lbTotal.setText(totalText);
                            btnGetTransferLog.setDisable(false);
                        });
                        return Double.valueOf(0);
                    }
                };
            }
        };
        service.start();
    }

    @FXML
    public void doConvertU() throws Exception {
        var items = getSelectedUserItems();
        if (null == items) {
            return;
        }
        HomeController homeController = (HomeController) Context.controllers.get(HomeController.class.getSimpleName());
        homeController.doOpenLog();
        LogController log = (LogController) Context.controllers.get(LogController.class.getSimpleName());

        btnConvertU.setDisable(true);
        Service<Double> service = new Service<Double>() {
            @Override
            protected Task<Double> createTask() {
                return new Task<Double>() {
                    @Override
                    protected Double call() {
                        JSONObject jsonObject;
                        for (TreeItem<UserItem> item : items) {
                            UserItem userItem = item.getValue();
                            try {
                                HttpResponse response = trService
                                    .convertU(userItem.getCookie(), userItem.getToken(), GoogleAuthenticatorUtils.getTOTPCode(userItem.getSec()), userItem.getPassword(), userItem.getBalanceW());
                                jsonObject = JSON.parseObject(response.body());
                                log.addLog("账号: %s 转让W钱包: %s 美金到U钱包 %s", userItem.getUsername(), userItem.getBalanceW(), jsonObject.getString("showTittle"));
                                boolean suc = false;
                                if ("0".equals(jsonObject.getString("result"))) {
                                    suc = true;
                                } else {
                                    response = trService
                                        .convertU2(userItem.getCookie(), userItem.getToken(), GoogleAuthenticatorUtils.getTOTPCode(userItem.getSec()), userItem.getPassword(), userItem.getBalanceW());
                                    jsonObject = JSON.parseObject(response.body());
                                    log.addLog("账号: %s 转让W钱包: %s 美金到U钱包重试 %s", userItem.getUsername(), userItem.getBalanceW(), jsonObject.getString("showTittle"));
                                    if ("0".equals(jsonObject.getString("result"))) {
                                        suc = true;
                                    }
                                }
                                if (suc) {
                                    User updUser = new User();
                                    updUser.setBalanceU(userItem.getBalanceW());
                                    updUser.setBalanceW(BigDecimal.ZERO);
                                    userDao.updateByPrimaryKey(updUser, userItem.getId());
                                    userItem.setBalanceW(BigDecimal.ZERO);
                                    userItem.setBalanceU(userItem.getBalanceW());
                                    log.addLog("账号: %s 本地数据更新成功", userItem.getUsername());
                                }
                            } catch (Exception e) {
                                log.addLog("账号: %s W转U失败 %s", userItem.getUsername(), e.getMessage());
                            }
                        }
                        btnConvertU.setDisable(false);
                        return Double.valueOf(0);
                    }
                };
            }
        };
        Platform.runLater(service::restart);
    }

    @FXML
    public void doGetRate() {
        JSONObject jsonObject = trService.getCNY();
        String totalText = String.format("$:%s(￥:%s) 汇率:%s", total, convertCny(jsonObject, total), jsonObject.get("rate"));
        StringBuilder text = new StringBuilder();
        totalGroup.forEach((k, balance) -> {
            text.append(String.format(" %s $:%s(￥:%s)", k, balance, convertCny(jsonObject, balance)));
        });
        lbTotal2.setText(text.toString());
        lbTotal.setText(totalText);
    }

    public BigDecimal convertCny(JSONObject jsonObject, BigDecimal doller) {
        return doller.multiply(new BigDecimal(jsonObject.get("rate").toString())).setScale(2, RoundingMode.HALF_UP);
    }

    @FXML
    public void doDel() {
        var items = tblUser.getSelectionModel().getSelectedItems();
        if (CollectionUtil.isEmpty(items)) {
            CommonUtil.alert(null, "你先选一个行么?");
            return;
        }
        if (CommonUtil.confirm(null, "操作确认", "确定要删除么?")) {
            items.forEach(item -> {
                if (1 == item.getValue().getId()) {
                    return;
                }
                userDao.deleteByPrimaryKey(item.getValue().getId());
            });
            bindData();
        }
    }

    @FXML
    public void doGetUser() {
        bindData();
    }

    private void bindData() {
        tblUser.getColumns().clear();

        //创建根节点
        UserItem rootItem = new UserItem();
        rootItem.setId(1L);
        rootItem.setUsername("全部");
        root = new CheckBoxTreeItem<>(rootItem);
        root.setExpanded(true);

        final List<User> users = userDao.selectAll(null);
        if (CollectionUtil.isNotEmpty(users)) {
            BigDecimal u = users.stream().filter(a -> a.getBalanceU() != null).map(User::getBalanceU).reduce(BigDecimal.ZERO, BigDecimal::add);
            BigDecimal w = users.stream().filter(a -> a.getBalanceW() != null).map(User::getBalanceW).reduce(BigDecimal.ZERO, BigDecimal::add);
            Map<String, List<User>> group = users.stream().collect(Collectors.groupingBy(User::getMaster));

            total = u;
            group.forEach((k, v) -> {
                BigDecimal balance = v.stream().filter(a -> a.getBalanceU() != null && a.getBalanceW() != null)
                    .map(User::getBalanceU).reduce(BigDecimal.ZERO, BigDecimal::add);
                totalGroup.put(k, balance);
            });
            calc(root, users, getByParentId(users, 0L));
        }

        //创建一个列
        TreeTableColumn<UserItem, Boolean> checklist = new TreeTableColumn<>("操作");
        //定义列的单元格内容
        checklist.setCellFactory(p -> {
            CheckBoxTreeTableCell cell = new CheckBoxTreeTableCell<>();
            cell.setSelectedStateCallback(new Callback<Integer, ObservableValue<Boolean>>() {
                @Override
                public ObservableValue<Boolean> call(Integer index) {
                    UserItem domain = tblUser.getTreeItem(index).getValue();
                    return new SimpleBooleanProperty();
                }
            });
            return cell;
        });

        //创建一个列
        TreeTableColumn<UserItem, String> colId = new TreeTableColumn<>("编号");
        colId.setPrefWidth(200);
        //定义列的单元格内容
        colId.setCellValueFactory((TreeTableColumn.CellDataFeatures<UserItem, String> p) ->
            new ReadOnlyStringWrapper(p.getValue().getValue().getId().toString()));

        //创建一个列
        TreeTableColumn<UserItem, String> colUsername = new TreeTableColumn<>("用户名");

        //定义列的单元格内容
        colUsername.setCellValueFactory((TreeTableColumn.CellDataFeatures<UserItem, String> p) ->
            new ReadOnlyStringWrapper(p.getValue().getValue().getUsername()));

        //创建一个列
        TreeTableColumn<UserItem, String> colPassword = new TreeTableColumn<>("密码");
        colPassword.setPrefWidth(150);

        //定义列的单元格内容
        colPassword.setCellValueFactory((TreeTableColumn.CellDataFeatures<UserItem, String> p) ->
            new ReadOnlyStringWrapper(p.getValue().getValue().getPassword()));

        //创建一个列
        TreeTableColumn<UserItem, String> colMaster = new TreeTableColumn<>("所属");

        //定义列的单元格内容
        colMaster.setCellValueFactory((TreeTableColumn.CellDataFeatures<UserItem, String> p) ->
            new ReadOnlyStringWrapper(p.getValue().getValue().getMaster()));

        //创建一个列
        TreeTableColumn<UserItem, String> colBalanceW = new TreeTableColumn<>("W钱包");

        //定义列的单元格内容
        colBalanceW.setCellValueFactory((TreeTableColumn.CellDataFeatures<UserItem, String> p) ->
            new ReadOnlyStringWrapper(CommonUtil.asString(p.getValue().getValue().getBalanceW())));

        //创建一个列
        TreeTableColumn<UserItem, String> colBalanceU = new TreeTableColumn<>("U钱包");

        //定义列的单元格内容
        colBalanceU.setCellValueFactory((TreeTableColumn.CellDataFeatures<UserItem, String> p) ->
            new ReadOnlyStringWrapper(CommonUtil.asString(p.getValue().getValue().getBalanceU())));

        //创建一个列
        TreeTableColumn<UserItem, String> colSec = new TreeTableColumn<>("安全码");
        colSec.setPrefWidth(150);

        //定义列的单元格内容
        colSec.setCellValueFactory((TreeTableColumn.CellDataFeatures<UserItem, String> p) ->
            new SimpleStringProperty(p.getValue().getValue().getSec()));

        //创建一个列
        TreeTableColumn<UserItem, String> colSecCode = new TreeTableColumn<>("安全码代码");
        colSecCode.setPrefWidth(150);

        //定义列的单元格内容
        colSecCode.setCellValueFactory((TreeTableColumn.CellDataFeatures<UserItem, String> p) ->
            new SimpleStringProperty(GoogleAuthenticatorUtils.getTOTPCode(p.getValue().getValue().getSec())));

        //创建一个列
        TreeTableColumn<UserItem, String> colToken = new TreeTableColumn<>("令牌");
        colToken.setPrefWidth(300);

        //定义列的单元格内容
        colToken.setCellValueFactory((TreeTableColumn.CellDataFeatures<UserItem, String> p) ->
            new SimpleStringProperty(p.getValue().getValue().getCookie()));

        //创建一个列
        TreeTableColumn<UserItem, String> colTokenTime = new TreeTableColumn<>("令牌时间");
        colTokenTime.setPrefWidth(100);

        //定义列的单元格内容
        colTokenTime.setCellValueFactory((TreeTableColumn.CellDataFeatures<UserItem, String> p) ->
            new ReadOnlyStringWrapper(CommonUtil.date2Str(p.getValue().getValue().getTokenTime())));

        //创建一个TreeTableView
        tblUser.setRoot(root);
        tblUser.getColumns().addAll(checklist, colId, colUsername, colMaster, colBalanceW, colBalanceU, colPassword, colSec, colSecCode, colToken, colTokenTime);
        tblUser.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
        tblUser.setShowRoot(false);
        tblUser.setEditable(true);
    }

    private void calc(TreeItem<UserItem> curTree, List<User> users, List<UserItem> userItems) {
        for (UserItem userItem : userItems) {
            TreeItem<UserItem> childTree = new CheckBoxTreeItem<>(userItem);
            childTree.setExpanded(true);
            curTree.getChildren().add(childTree);
            var childUserItems = getByParentId(users, userItem.getId());
            if (CollectionUtil.isNotEmpty(childUserItems)) {
                calc(childTree, users, childUserItems);
            }
        }
    }

    public List<UserItem> getByParentId(List<User> users, Long parentId) {
        if (CollectionUtil.isEmpty(users)) {
            return Collections.emptyList();
        }
        return users.stream().filter(u -> u.getParentId().equals(parentId))
            .map(u -> BeanUtil.copyProperties(u, UserItem.class)).collect(Collectors.toList());
    }

}
